package com.cloudtec.yunzonedep.config.shiro;

import cn.hutool.json.JSON;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.cloudtec.yunzonedep.common.entity.ConstantPool;
import com.cloudtec.yunzonedep.common.util.JWTUtil;
import com.cloudtec.yunzonedep.common.util.RedisUtil;
import com.cloudtec.yunzonedep.modules.user.entity.SysUser;
import com.cloudtec.yunzonedep.modules.user.service.SysUserService;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @Description: 鉴权登录拦截器
 * @Author: Scott
 * @Date: 2018/10/7
 **/
@Slf4j
@Data
@Accessors(chain = true)
public class JWTFilter extends BasicHttpAuthenticationFilter {
    //无法自动注入，需要从ShiroConfig中注入，或者可以通过注入JWTFilter的方式省去注入redisUtil和sysUserService

    private RedisUtil redisUtil;

    private SysUserService sysUserService;

    public JWTFilter(RedisUtil redisUtil, SysUserService sysUserService) {
        this.redisUtil = redisUtil;
        this.sysUserService = sysUserService;
    }

    /**
     * 检测Header里Authorization字段
     * 判断是否登录
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader(ConstantPool.HTTP_AUTH_HEADER);
        return authorization != null;
    }

    /**
     * 执行登录认证
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            executeLogin(request, response);
            return true;
        } catch (Exception e) {
            throw new AuthenticationException("??Token失效，请重新登录", e);
        }
    }

    /**
     *
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        //转化request对象
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        //转化response对象
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        //从request中获取请求头部的Authorization的token
        String token = httpServletRequest.getHeader(ConstantPool.HTTP_AUTH_HEADER);
        //构建一个JwtToken对象
        JwtToken jwtToken = new JwtToken(token);
        //提交给realm进行登录，如果错误他会抛出异常并被捕获
        //log.info("执行到这里的login方法了,token:{}", token);
        getSubject(request, response).login(jwtToken);
        jwtTokenRefresh(httpServletRequest, httpServletResponse);
        // 如果没有抛出异常则代表登入成功，返回true
        return true;
    }

    /**
     * JWTToken刷新生命周期 （解决用户一直在线操作，提供Token失效问题）
     * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样)
     * 2、当该用户再次请求时，通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
     * 3、当该用户这次请求JWTToken值还在生命周期内，则会通过重新PUT的方式k、v都为Token值，缓存中的token值生命周期时间重新计算(这时候k、v值一样)
     * 4、当该用户这次请求jwt生成的token值已经超时，但该token对应cache中的k还是存在，则表示该用户一直在操作只是JWT的token失效了，程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值，该缓存生命周期重新计算
     * 5、当该用户这次请求jwt在生成的token值已经超时，并在cache中不存在对应的k，则表示该用户账户空闲超时，返回用户信息已失效，请重新登录。
     * 6、每次当返回为true情况下，都会给Response的Header中设置Authorization，该Authorization映射的v为cache对应的v值。
     * 7、注：当前端接收到Response的Header中的Authorization值会存储起来，作为以后请求token使用
     * 参考方案：https://blog.csdn.net/qq394829044/article/details/82763936
     */
    public void jwtTokenRefresh(HttpServletRequest request, HttpServletResponse response) {
        //从request中获取请求头部的Authorization的token
        String token = request.getHeader(ConstantPool.HTTP_AUTH_HEADER);
        String username = JWTUtil.getUsername(token);
        //System.out.println("通过token获取的username:" + username);
        SysUser sysUser = sysUserService.getUserByName(username);
        // 校验token有效性
        if (!JWTUtil.verify(token, username, sysUser.getPassword())) {
            System.out.println("token过期了");
            //如果无效了，重新生成一个token
            String newToken = JWTUtil.sign(username, sysUser.getPassword());
            String key = username + ConstantPool.LOGINTAG + newToken;
            redisUtil.set(key, newToken);
            // 设置超时时间
            redisUtil.expire(key, JWTUtil.EXPIRE_TIME / 1000);
            //返回给response
            response.setHeader(ConstantPool.HTTP_AUTH_HEADER, newToken);
        } else {
            System.out.println("token依然有效");
            //如果依然有效，重新放入，相当更新一下，相当于把过期时间重置了一下，依然是60min
            String key = username + ConstantPool.LOGINTAG + token;
            redisUtil.set(key, token);
            // 设置超时时间
            redisUtil.expire(key, JWTUtil.EXPIRE_TIME / 1000);
            response.setHeader(ConstantPool.HTTP_AUTH_HEADER, token);
        }


    }


    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization");
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 401非法请求
     *
     * @param req
     * @param resp
     */
    private void response401(ServletRequest req, ServletResponse resp, String msg) {
        HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        PrintWriter out = null;
		/*try {
			out = httpServletResponse.getWriter();

			Result result = new Result();
			result.setResult(false);
			result.setCode(Constants.PASSWORD_CHECK_INVALID);
			result.setMessage(msg);
			out.append(JSON.toJSONString(result));
		} catch (IOException e) {
			LOGGER.error("返回Response信息出现IOException异常:" + e.getMessage());
		} finally {
			if (out != null) {
				out.close();
			}
		}*/
    }

    public SysUserService getSysUserService() {
        return sysUserService;
    }

    public void setSysUserService(SysUserService sysUserService) {
        this.sysUserService = sysUserService;
    }

    public RedisUtil getRedisUtil() {
        return redisUtil;
    }

    public void setRedisUtil(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }


}
